home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / invest / quotes.py < prev    next >
Text File  |  2009-10-20  |  8KB  |  230 lines

  1. from os.path import join
  2. import gnomeapplet, gtk, gtk.gdk, gconf, gobject
  3. from gettext import gettext as _
  4. import csv
  5. from urllib import urlopen
  6. import datetime
  7. from threading import Thread
  8.  
  9. import invest, invest.about, invest.chart
  10.  
  11. CHUNK_SIZE = 512*1024 # 512 kB
  12. AUTOREFRESH_TIMEOUT = 15*60*1000 # 15 minutes
  13.  
  14. QUOTES_URL="http://finance.yahoo.com/d/quotes.csv?s=%(s)s&f=sl1d1t1c1ohgv&e=.csv"
  15.  
  16. # Sample (25/4/2008): UCG.MI,"4,86",09:37:00,2008/04/25,"0,07","4,82","4,87","4,82",11192336
  17. QUOTES_CSV_FIELDS=["ticker", ("trade", float), "time", "date", ("variation", float), ("open", float)]
  18.  
  19. # based on http://www.johnstowers.co.nz/blog/index.php/2007/03/12/threading-and-pygtk/
  20. class _IdleObject(gobject.GObject):
  21.     """
  22.     Override gobject.GObject to always emit signals in the main thread
  23.     by emmitting on an idle handler
  24.     """
  25.     def __init__(self):
  26.         gobject.GObject.__init__(self)
  27.  
  28.     def emit(self, *args):
  29.         gobject.idle_add(gobject.GObject.emit,self,*args)
  30.  
  31. class QuotesRetriever(Thread, _IdleObject):
  32.     """
  33.     Thread which uses gobject signals to return information
  34.     to the GUI.
  35.     """
  36.     __gsignals__ =  {
  37.             "completed": (
  38.                 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
  39.             # FIXME: We don't monitor progress, yet ...
  40.             #"progress": (
  41.             #    gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
  42.             #    gobject.TYPE_FLOAT])        #percent complete
  43.             }
  44.  
  45.     def __init__(self, tickers):
  46.         Thread.__init__(self)
  47.         _IdleObject.__init__(self)
  48.         self.tickers = tickers
  49.         self.retrieved = False
  50.         self.data = []
  51.  
  52.     def run(self):
  53.         quotes_url = QUOTES_URL % {"s": self.tickers}
  54.         try:
  55.             quotes_file = urlopen(quotes_url, proxies = invest.PROXY)
  56.             self.data = quotes_file.readlines ()
  57.             quotes_file.close ()
  58.         except Exception, msg:
  59.             invest.debug("Error while retrieving quotes data (url = %s): %s" % (quotes_url, msg))
  60.         else:
  61.             self.retrieved = True
  62.         self.emit("completed")
  63.  
  64.  
  65. class QuoteUpdater(gtk.ListStore):
  66.     updated = False
  67.     last_updated = None
  68.     quotes_valid = False
  69.     SYMBOL, LABEL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT, PB = range(8)
  70.     def __init__ (self, change_icon_callback, set_tooltip_callback):
  71.         gtk.ListStore.__init__ (self, gobject.TYPE_STRING, gobject.TYPE_STRING, bool, float, float, float, float, gtk.gdk.Pixbuf)
  72.         gobject.timeout_add(AUTOREFRESH_TIMEOUT, self.refresh)
  73.         self.change_icon_callback = change_icon_callback
  74.         self.set_tooltip_callback = set_tooltip_callback
  75.         self.set_sort_column_id(1, gtk.SORT_ASCENDING)
  76.         self.refresh()
  77.  
  78.     def refresh(self):
  79.         if len(invest.STOCKS) == 0:
  80.             return True
  81.  
  82.         tickers = '+'.join(invest.STOCKS.keys())
  83.         quotes_retriever = QuotesRetriever(tickers)
  84.         quotes_retriever.connect("completed", self.on_retriever_completed)
  85.         quotes_retriever.start()
  86.  
  87.         return True
  88.  
  89.  
  90.     def on_retriever_completed(self, retriever):
  91.         if retriever.retrieved == False:
  92.             tooltip = [_('Invest could not connect to Yahoo! Finance')]
  93.             if self.last_updated != None:
  94.                 tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
  95.             self.set_tooltip_callback('\n'.join(tooltip))
  96.         else:
  97.             self.populate(self.parse_yahoo_csv(csv.reader(retriever.data)))
  98.             self.updated = True
  99.             self.last_updated = datetime.datetime.now()
  100.             tooltip = []
  101.             if self.simple_quotes_count > 0:
  102.                 # Translators: This is share-market jargon. It is the percentage change in the price of a stock. The %% gets changed to a single percent sign and the %+.2f gets replaced with the value of the change.
  103.                 tooltip.append(_('Quotes average change %%: %+.2f%%') % self.avg_simple_quotes_change)
  104.             if self.positions_count > 0:
  105.                 # Translators: This is share-market jargon. It refers to the total difference between the current price and purchase price for all the shares put together. i.e. How much money would be earned if they were sold right now.
  106.                 tooltip.append(_('Positions balance: %+.2f') % self.positions_balance)
  107.             tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
  108.             self.set_tooltip_callback('\n'.join(tooltip))
  109.  
  110.  
  111.  
  112.     def parse_yahoo_csv(self, csvreader):
  113.         result = {}
  114.         for fields in csvreader:
  115.             if len(fields) == 0:
  116.                 continue
  117.  
  118.             result[fields[0]] = {}
  119.             for i, field in enumerate(QUOTES_CSV_FIELDS):
  120.                 if type(field) == tuple:
  121.                     try:
  122.                         result[fields[0]][field[0]] = field[1](fields[i])
  123.                     except:
  124.                         result[fields[0]][field[0]] = 0
  125.                 else:
  126.                     result[fields[0]][field] = fields[i]
  127.             # calculated fields
  128.             try:
  129.                 result[fields[0]]['variation_pct'] = result[fields[0]]['variation'] / float(result[fields[0]]['trade'] - result[fields[0]]['variation']) * 100
  130.             except ZeroDivisionError:
  131.                 result[fields[0]]['variation_pct'] = 0
  132.         return result
  133.  
  134.     def populate(self, quotes):
  135.         if (len(quotes) == 0):
  136.             return
  137.  
  138.         self.clear()
  139.         
  140.         try:
  141.             quote_items = quotes.items ()
  142.             quote_items.sort ()
  143.  
  144.             simple_quotes_change = 0
  145.             self.simple_quotes_count = 0
  146.             self.positions_balance = 0
  147.             self.positions_count = 0
  148.  
  149.             for ticker, val in quote_items:
  150.                 pb = None
  151.  
  152.                 # get the label of this stock for later reuse
  153.                 label = invest.STOCKS[ticker]["label"]
  154.                 if len(label) == 0:
  155.                     label = ticker
  156.  
  157.                 # Check whether the symbol is a simple quote, or a portfolio value
  158.                 is_simple_quote = True
  159.                 for purchase in invest.STOCKS[ticker]["purchases"]:
  160.                     if purchase["amount"] != 0:
  161.                         is_simple_quote = False
  162.                         break
  163.  
  164.                 if is_simple_quote:
  165.                     self.simple_quotes_count += 1
  166.                     row = self.insert(0, [ticker, label, True, 0, 0, val["trade"], val["variation_pct"], pb])
  167.                     simple_quotes_change += val['variation_pct']
  168.                 else:
  169.                     self.positions_count += 1
  170.                     current = sum([purchase["amount"]*val["trade"] for purchase in invest.STOCKS[ticker]["purchases"] if purchase["amount"] != 0])
  171.                     paid = sum([purchase["amount"]*purchase["bought"] + purchase["comission"] for purchase in invest.STOCKS[ticker]["purchases"] if purchase["amount"] != 0])
  172.                     balance = current - paid
  173.                     if paid != 0:
  174.                         change = 100*balance/paid
  175.                     else:
  176.                         change = 100 # Not technically correct, but it will look more intuitive than the real result of infinity.
  177.                     row = self.insert(0, [ticker, label, False, balance, change, val["trade"], val["variation_pct"], pb])
  178.                     self.positions_balance += balance
  179.  
  180.                 if len(ticker.split('.')) == 2:
  181.                     url = 'http://ichart.europe.yahoo.com/h?s=%s' % ticker
  182.                 else:
  183.                     url = 'http://ichart.yahoo.com/h?s=%s' % ticker
  184.  
  185.                 image_retriever = invest.chart.ImageRetriever(url)
  186.                 image_retriever.connect("completed", self.set_pb_callback, row)
  187.                 image_retriever.start()
  188.  
  189.             if self.simple_quotes_count > 0:
  190.                 self.avg_simple_quotes_change = simple_quotes_change/float(self.simple_quotes_count)
  191.             else:
  192.                 self.avg_simple_quotes_change = 0
  193.  
  194.             if self.avg_simple_quotes_change != 0:
  195.                 simple_quotes_change_sign = self.avg_simple_quotes_change / abs(self.avg_simple_quotes_change)
  196.             else:
  197.                 simple_quotes_change_sign = 0
  198.  
  199.             # change icon
  200.             if self.simple_quotes_count > 0:
  201.                 self.change_icon_callback(simple_quotes_change_sign)
  202.             else:
  203.                 positions_balance_sign = self.positions_balance/abs(self.positions_balance)
  204.                 self.change_icon_callback(positions_balance_sign)
  205.                 
  206.             # mark quotes to finally be valid
  207.             self.quotes_valid = True
  208.  
  209.         except Exception, msg:
  210.             invest.debug("Failed to populate quotes: %s" % msg)
  211.             invest.debug(quotes)
  212.             self.quotes_valid = False
  213.  
  214.     def set_pb_callback(self, retriever, row):
  215.         self.set_value(row, self.PB, retriever.image.get_pixbuf())
  216.  
  217.     # check if we have only simple quotes
  218.     def simple_quotes_only(self):
  219.         res = True
  220.         for entry, data in invest.STOCKS.iteritems():
  221.             purchases = data["purchases"]
  222.             for purchase in purchases:
  223.                 if purchase["amount"] != 0:
  224.                     res = False
  225.                     break
  226.         return res
  227.  
  228. if gtk.pygtk_version < (2,8,0):
  229.     gobject.type_register(QuoteUpdater)
  230.